/*
尽可能还原 Promise 中的每一个 API, 并通过注释的方式描述思路和原理.
*/

// 这里定义promise三种状态的常量，以便复用
const PENDING = 'pending' // 等待状态
const FULFILLED = 'fulfilled' // 成功状态
const REJECTED = 'rejected' // 失败状态

class MyPromise {
  // promise构造器，接收一个执行器函数
  constructor (executor) {
    // 这里对执行器使用try catch，以捕获执行器执行时可能出现的错误
    // 并且在 catch 中直接调用 reject 更改为失败状态，并且传递失败的错误信息
    try {
      // 创建promise对象时，立刻执行该执行器函数，并且给它传递resolve、reject函数参数，分别用于更改promise状态为成功和失败
      executor(this.resolve, this.reject)
    } catch (e) {
      this.reject(e)
    }
  }

  // promise实例状态属性，默认为“等待”状态
  status = PENDING
  // 成功后的值，由 resolve 函数接收得到
  value = undefined
  // 失败后的原因，由 reject 函数接收得到
  reason = undefined
  // 针对异步的情况，也就是执行器函数中的代码是异步的，resolve方法不会立即调用，而then方法是立即执行的
  // 所以这里需要将then方法中的成功回调保存起来，等待异步代码执行完成后，再调用成功回调
  // 由于then是可以链式调用的，所以这里定义为数组
  successCallback = []
  // 同上，存储的是then中的失败回调
  failCallback = []

  // 通过执行器回调函数进行调用，用于成功后的处理
  // 这里使用箭头函数是为了里面的this可以指向当前promise对象
  resolve = value => {
    // 由于promise状态一旦更改后就不能再变了，
    // 所以这里需要判断状态是不是“等待”，如果不是，则阻止程序向下执行
    if (this.status !== PENDING) return
    // 将状态更改为成功
    this.status = FULFILLED
    // 保存成功后的值，用于then方法成功回调能接收到
    this.value = value
    // 判断then成功回调是否存在，如果存在则调用
    // 通过 shift 方法把成功回调数组中的第一个回调移除（也就是要先进先出），并且得到后直接通过加上小括号执行该函数
    while (this.successCallback.length) this.successCallback.shift()()
  }

  // 通过执行器回调函数进行调用，用于成功后的处理
  // 这里使用箭头函数是为了里面的this可以指向当前promise对象
  reject = reason => {
    // 由于promise状态一旦更改后就不能再变了，
    // 所以这里需要判断状态是不是“等待”，如果不是，则阻止程序向下执行
    if (this.status !== PENDING) return
    // 将状态更改为失败
    this.status = REJECTED
    // 保存失败后的原因，用于then方法失败回调能接收到
    this.reason = reason
    // 判断then失败回调是否存在，如果存在则调用
    // 通过 shift 方法把失败回调数组中的第一个回调移除（也就是要先进先出），并且得到后直接通过加上小括号执行该函数
    while (this.failCallback.length) this.failCallback.shift()()
  }

  // then方法内部做的事情就是判断状态，如果状态是成功则调用成功的回调函数，如果状态是失败则调用失败回调函数
  // 其中成功回调有一个参数，表示成功之后的值，而失败回调有一个参数，表示失败后的原因
  then = (successCallback, failCallback) => {
    // then 方法的参数是可选的，也就是可以不用传递参数，但这时又需要将promise状态传递下去，直到有回调函数的then
    // 所以这里判断是否有回调，如果没有，则给一个默认的，把成功的值和失败的原因传递下去
    successCallback = successCallback ? successCallback : value => value
    failCallback = failCallback ? failCallback : reason => { throw reason }
    // 由于then方法可以链式调用，所以这里应该返回一个promise对象
    // 因此把关于promise状态判断以及执行回调的代码放到返回的promise对象的执行器函数中，因为执行器函数是立即执行的
    let promsie2 = new MyPromise((resolve, reject) => {
      // 判断当前promise状态，以便确认是调用成功回调还是失败回调
      if (this.status === FULFILLED) { // 成功状态，调用成功回调
        // 这里使用 setTimeout 不是为了延迟，而是把成功状态后的执行代码变为异步执行，
        // 因为这里需要拿到 promsie2 变量，也就是当前 then 方法返回的promise对象，
        // 拿到 promsie2 是为了和上一个 then 方法回调函数返回的值 x 作比较，
        // 而之所以要做比较，是判断上一个 then 回调中是否返回了它自身的那个promise对象，如果是的话则直接调用失败回调报错（具体实现在 resolvePromise 函数中）
        setTimeout(() => {
          // 这里对 then 成功回调方法执行错误进行捕获，
          // 若报错则直接调用下一promise的reject（也就是当前返回的promise对象的reject）
          try {
            // 由于then方法的回调函数中拿到的值是上一个then方法回调函数的返回值，
            // 所以这里需要接收一下回调函数返回的值，以便传给下一个then方法的回调
            let x = successCallback(this.value)
            // 这里需要判断 x 的值是普通值还是promise对象
            // 如果是普通值则直接调用resolve 
            // 如果是promise对象，则查看promsie对象返回的结果，再根据结果决定调用 resolve 还是调用 reject
            // 由于这个判断处理过程，在下面失败状态、等待状态都有用到，所以封装为一个函数进行调用
            resolvePromise(promsie2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      } else if (this.status === REJECTED) { // 失败状态，调用失败回调
        // 这里使用 setTimeout，原理同上
        setTimeout(() => {
          // 对失败回调的异常捕获
          try {
            // 同上
            let x = failCallback(this.reason)
            // 同上
            resolvePromise(promsie2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0)
      } else {
        // 其它情况也就是还处于等待状态，说明是异步代码
        // 这时需要将成功和失败回调存储起来，等待异步代码执行完成后，再进行调用

        // 而这里之所以不直接将 successCallback 函数存储，而是通过外面在包裹了一个函数，是因为：
        // -1.这里需要接收成功回调的返回值做处理
        // -2.同时需要对成功回调的执行进行异常捕获
        this.successCallback.push(() => {
          // 这里使用 setTimeout，原理同上
          setTimeout(() => {
            // 异常捕获,同上
            try {
              // 同上
              let x = successCallback(this.value)
              // 同上
              resolvePromise(promsie2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })
        // 原理同上面的成功回调
        this.failCallback.push(() => {
          // 这里使用 setTimeout，原理同上
          setTimeout(() => {
            // 异常捕获,同上
            try {
              // 同上
              let x = failCallback(this.reason)
              // 同上
              resolvePromise(promsie2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0)
        })
      }
    })
    return promsie2
  }

  // 无论当前 promise 的状态是成功还是失败，这个 finally 方法始终都会被执行一次
  // finally 方法后可以链式调用 then 方法，所以需要返回一个 promise 对象
  finally (callback) {
    // 由于不管 promise 的状态是成功还是失败，都会调用 finally 的回调函数 callback，所以这里需要拿到 promise 的状态
    // 这里可以通过调用当前promise对象的 then 方法来进行判断，在 then 的成功和失败回调中都调用 callback 
    // 而由于 finally 的回调函数中可能返回的是普通值或者 promise 对象，如果是普通值，那么在 then 的回调中可以直接调用 callback，然后 return value
    // 但如果是 promise 对象的话，这样直接返回 value 就不行，因为下一个then方法不会等待当前这个 promise 执行完成
    // 所以这里可以通过调用 MyPromise.resolve 方法，并且将 callback 函数的执行结果作为参数传递，那么不管 callback 的执行结果是什么，通过 MyPromise.resolve 方法都是返回了 promise 对象
    return this.then(value => {
      return MyPromise.resolve(callback()).then(() => value)
    }, reason => {
      return MyPromise.resolve(callback()).then(() => { throw reason })
    })
  }

  // catch 方法用来处理当前 promise 对象最终的状态为失败的情况，接收一个处理失败回调的参数
  // 也就是所有 then 方法都可以不传递失败回调，那么这里失败回调就需要被 catch 所捕获
  catch (failCallback) {
    // 直接调用 then 方法即可，因为通过 then 可以拿到当前 promise 状态
    // 这里不传递成功回调，而是把 catch 的失败回调作为 then 的失败回调即可
    // 而 catch 后还可以继续链式调用 then 方法，所以这里直接将 then 的结果返回即可
    return this.then(undefined, failCallback)
  }

  // all 为静态方法，接收一个数组，数组里的值可能是普通值或者promise对象
  static all (array) {
    // all 方法最终返回的结果数组
    let result = []
    // 用于标记成功结果数，也就是array参数中普通值或promise对象执行结果的成功数
    let index = 0
    // all 方法的返回值为promise对象，所以这里创建一个promise对象并返回
    // 然后在执行器中对 array 数组参数进行遍历，拿到数组中的普通值或者promise对象执行的结果，按顺序放到结果数组中进行返回
    // 只有参数中所有promise的状态都是成功的，all方法才是成功的，否则就是失败
    return new MyPromise((resolve, reject) => {
      // 将 array 中每个值的执行结果添加到结果数组中的方法
      // i 表示对应的下标，v 表示结果值
      function addData (i, v) {
        result[i] = v
        // 能执行该方法则表示执行结果是成功的，所以 index 加1
        index ++
        // 如果 index 等于 array 的长度，则表示all方法最终的结果是成功的，直接调用 resolve，并传递执行结果 result
        if (index === array.length) {
          resolve(result)
        }
      }
      // 遍历 array 参数，判断值时普通值还是promise对象
      // 如果是 promise 对象则直接调用 then 方法，并且定义then的成功和失败回调
      // 在成功回调中将该promise对象的执行结果通过 addData 方法添加到结果数组result
      // 在失败回调中直接调用 reject 方法，传递失败的原因
      for (let i = 0; i < array.length; i++) {
        const current = array[i];
        if (current instanceof MyPromise) {
          // promise对象，则在then的成功方法中把值放到返回结果中，如果是失败则直接调用失败回调
          current.then(value => addData(i, value), reason => reject(reason))
        } else {
          // 普通值，则直接放到返回结果中
          addData(i, current)
        }
      }
    })
  }

  // resolve 为静态方法，接收一个值
  // resolve方法返回值是一个 promise 对象
  static resolve (value) {
    // 判断接收的值是否为 promise 对象，如果是则直接方法
    if (value instanceof MyPromise) return value
    // 如果是普通值，则创建一个 promise 对象，传递一个执行器，在执行器中通过 resolve 将 value 返回即可
    return new MyPromise(resolve => resolve(100))
  }
}

function resolvePromise (promise2, x, resolve, reject) {
  // 这里需要判断是否为同一个 promise 对象，如果是则直接调用失败 reject 并传递错误信息，这是为了防止循环调用，自返回的情况
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }
  if (x instanceof MyPromise) {
    // promise 对象，通过调用该对象的then方法，并且给它传递 resolve 和 reject，让其内部根据状态来判断调用哪个
    x.then(resolve, reject)
  } else {
    // 普通值，直接调用 resolve
    resolve(x)
  }
}